package { 
	import flash.geom.Point;
	import flash.net.SharedObject;
	import flash.utils.Dictionary;
	
	import org.flixel.*;
	
	public class GameState extends FlxState {
		
		//Cookie
		protected var localInformation:SharedObject;
		
		//Music and Mute Button.
		protected var music:String;
		protected var playIt:Boolean;
		protected var musicIcon:Music;
		protected var replayIco:FlxSprite;
		protected var ExitButton:FlxSprite;
		
		// Game sizing constants
		public static const CELL_SIZE:int = 46;
		public static const ROWS:int = 11;
		public static const COLS:int = 11;
		public static const X_OFFSET:int = 5;
		public static const Y_OFFSET:int = 5;
		
		// includes the bounding bars that contribute to the offset.
		public static const GRID_WIDTH:int = (CELL_SIZE * COLS) + (2 * X_OFFSET);
		public static const GRID_HEIGHT:int = (CELL_SIZE * ROWS) + (2 * Y_OFFSET);
		
		protected var theOverlay:FlxSprite;
		protected var hintbox:HintBox;
		
		// Index -> item, row major order for indices
		protected var tileMap:Dictionary;
		protected var arrowMap:Dictionary;
		protected var Ground:FlxGroup;
		protected var Blocks:FlxGroup;
		protected var Tiles:FlxGroup;
		protected var Sources:FlxGroup;
		protected var UnlitSources:FlxGroup;
		protected var SourceLightAnims:FlxGroup;
		
		protected var Forges:FlxGroup;
		protected var TransformSprites:FlxGroup;
		protected var Beams:FlxGroup;
		protected var obsImages:FlxGroup;
		protected var bin:ArrowBin;
		protected var arrowGroup:FlxGroup;
		protected var Houses:FlxGroup;
		protected var player:Player = null;
		protected var playerImageSprite:FlxSprite = null;
		protected var treasure:Point = null;
		protected var house:PoofHouse;
		protected var treasureObj:FlxTileblock = null;
		protected var train:Train = null;
		protected var collideTrain:FlxGroup = null;
		protected var obs:FlxGroup;
		protected var Monsters:FlxGroup;
		protected var NPCGroup:FlxGroup;
		protected var ExitingNPCGroup:FlxGroup;
		protected var CurrentMap:int;
		
		protected var playerLastX:int;
		protected var playerLastY:int;
		
		protected var FinalBoss:FlxSprite = null;
		
		
		//Text Control Variables
		protected var OpeningText:Array = new Array();
		protected var starting:Boolean;
		protected var oCount:int = 0;
		protected var EndingText:Array = new Array();
		protected var ending:Boolean;
		protected var eCount:int = 0;
		protected var StoryText:FlxText;
		protected var StoryNPC:NPC;
		protected var NPCText:FlxText;
		
		protected var NPCMessageUp:Boolean = false;
		protected var MessageCounter:int = 10;
		
		//beam sound control:
		protected var arrowLastPlaced:int = -1;
		
		// The CHEAT SHEET 
		protected var cheatSheet:CheatSheet;
		protected var cheatButton:FlxSprite;
		
		//loggin
		private var log:Logging = Elemental.Log;
		
		//Lock
		protected var MonsterLock:Boolean;
		
		protected var RightArrowSprite:BouncyArrow;
		protected var inMapArrow:BouncyArrow;
		protected var ArrowCountDown:int;
		protected var cheatSheetOpen:Boolean = false;
		protected var LevelComplete:FlxSprite;
		protected var Complete:Boolean;
		
		protected var LevelCompleteDelay:int;
		
		//achievements
		protected var achievesTimer:Number;
		protected var achievDisplayed:Boolean;
		protected var achievCurr:AchievementReport;
		
		protected var pacifist:Boolean;
		
		public function GameState(theMap:int) {
			starting = true;
			ending = false;
			this.add(new FlxSprite(0,0, Res.bg));
			playIt = false;
			LevelCompleteDelay = 100;
			//Initiate music and player options to turn it off.
			localInformation = SharedObject.getLocal("elementalGameInfo");
			
			//achievement check for reaching level
			achievesTimer = 0;
			achievDisplayed = false;
			
			if (theMap > 30) {
				music = Res.gokun;//gokun;
			} else if (theMap > 15) {
				music = Res.barrenS;
			} else {
				music = Res.forest;//forest;
			}
			
			//hint box and replay
			hintbox = new HintBox(521, 189, Elemental.TText[theMap]);
			add(hintbox);
			replayIco = new FlxSprite(642, 0, Res.replay);
			ExitButton = new FlxSprite(700-92, 35, Res.exit);
			
			musicIcon = new Music(676,0);
			
			this.add(musicIcon);
			this.add(replayIco);
			this.add(ExitButton);
			
			CurrentMap = theMap;
			var MapSpec:Class = Elemental.Maps[theMap];
			var temp:Array = Elemental.Arrows[theMap];
			var trainString:Array = Elemental.Trains[theMap];
			var ArrowArray:Array = new Array(temp[0], temp[1], temp[2], temp[3]);
			var theNPCs:Array = Elemental.NPCs[theMap];
			OpeningText = Elemental.SText[theMap];
			EndingText = Elemental.EText[theMap];
			
			tileMap = new Dictionary();
			arrowMap = new Dictionary();
			
			//Initialize Map for reading
			var map:String = new MapSpec;
			var mapArray:Array = map.split("\n").join("").split("\r").join("").split(",");
			
			//Initialize Flx Groups
			Ground = new FlxGroup();
			Blocks = new FlxGroup();
			Tiles = new FlxGroup();
			Sources = new FlxGroup();
			UnlitSources = new FlxGroup();
			SourceLightAnims = new FlxGroup();
			Forges = new FlxGroup();
			TransformSprites = new FlxGroup();
			Beams = new FlxGroup();
			obsImages = new FlxGroup();
			obs = new FlxGroup();
			collideTrain = new FlxGroup();
			Monsters = new FlxGroup();
			arrowGroup = new FlxGroup();
			NPCGroup = new FlxGroup();
			ExitingNPCGroup = new FlxGroup();
			Houses = new FlxGroup();
			
			var tBound:FlxTileblock = new FlxTileblock(0, 0, GRID_WIDTH, Y_OFFSET);
			var bBound:FlxTileblock = new FlxTileblock(0, GRID_HEIGHT - Y_OFFSET, GRID_WIDTH, Y_OFFSET);
			var lBound:FlxTileblock = new FlxTileblock(0, 0, X_OFFSET, GRID_HEIGHT);
			var rBound:FlxTileblock = new FlxTileblock(GRID_WIDTH - X_OFFSET, 0, X_OFFSET, GRID_HEIGHT);
			tBound.loadTiles(Res.topBoundImage, GRID_WIDTH,Y_OFFSET, 0);
			bBound.loadTiles(Res.bottomBoundImage, GRID_WIDTH,Y_OFFSET, 0);
			lBound.loadTiles(Res.leftBoundImage, X_OFFSET, GRID_HEIGHT, 0);
			rBound.loadTiles(Res.rightBoundImage, X_OFFSET, GRID_HEIGHT, 0);
			Blocks.add(tBound);
			Blocks.add(lBound);
			Blocks.add(rBound);
			Blocks.add(bBound);
			
			//Parse and create map
			for (var i:int = 0; i < ROWS; i++) {
				for (var j:int = 0; j < COLS; j++) {
					var index:int = (i*COLS) + j;
					var pos:Point = getPointFromIndex(index);
					var g:FlxTileblock;
					g = new FlxTileblock(pos.x, pos.y, CELL_SIZE, CELL_SIZE);
					if (CurrentMap > 30) {
						g.loadTiles(Res.voidTiles, CELL_SIZE, CELL_SIZE, 0);
					} else if (CurrentMap > 15) {
						g.loadTiles(Res.barrenTiles, CELL_SIZE, CELL_SIZE, 0);
					} else {
						g.loadTiles(Res.grassTiles, CELL_SIZE, CELL_SIZE, 0);
					}
					Ground.add(g);
					if (mapArray[index] == "1" || mapArray[index] == "2" || mapArray[index] == "3") {
						var b:FlxTileblock;
						b = new FlxTileblock(pos.x, pos.y, CELL_SIZE,CELL_SIZE);
						if (mapArray[index] == "1") {
							b.loadTiles(Res.grassWalls, CELL_SIZE, CELL_SIZE, 0);
						} else if (mapArray[index] == "2") {
							b.loadTiles(Res.barrenWalls, CELL_SIZE, CELL_SIZE, 0);
						} else {
							b.loadTiles(Res.voidWalls, CELL_SIZE, CELL_SIZE, 0);
						}
						Blocks.add(b);
						tileMap[index] = b;
					} else if (mapArray[index] == "100") {
						var sF:SourceForge
						var tranAnim:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						TransformSprites.add(tranAnim);
						sF = new SourceForge(pos.x, pos.y, tranAnim);
						Tiles.add(sF);
						Forges.add(sF);
						tileMap[index] = sF;
					} else if (mapArray[index] == "20") {
						var lightAnim0:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim0);
						var ws:WaterSource = new WaterSource(pos.x, pos.y, lightAnim0, true);
						ws.turnOn();
						Sources.add(ws);
						tileMap[index] = ws;
					} else if (mapArray[index] == "21") {
						var lightAnim1:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim1);
						var wis:WindSource = new WindSource(pos.x, pos.y, lightAnim1);
						wis.turnOn();
						Sources.add(wis);
						tileMap[index] = wis;
					} else if (mapArray[index] == "22") {
						var lightAnim2:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim2);
						var es:EarthSource = new EarthSource(pos.x, pos.y, lightAnim2);
						es.turnOn();
						Sources.add(es);
						tileMap[index] = es;
					} else if (mapArray[index] == "23") {
						var lightAnim3:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim3);
						var ls:LightSource = new LightSource(pos.x, pos.y, lightAnim3);
						ls.turnOn();
						Sources.add(ls);
						tileMap[index] = ls;
					} else if (mapArray[index] == "24" || mapArray[index] == "28") {
						var lightAnim4:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim4);
						var uws:WaterSource = new WaterSource(pos.x, pos.y, lightAnim4);
						UnlitSources.add(uws);
						tileMap[index] = uws;
						if (mapArray[index] == "28") {
							uws.turnOn();
						}
					} else if (mapArray[index] == "25" || mapArray[index] == "29") {
						var lightAnim5:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim5);
						var uwis:WindSource = new WindSource(pos.x, pos.y, lightAnim5);
						UnlitSources.add(uwis);
						tileMap[index] = uwis;
						if (mapArray[index] == "29") {
							uwis.turnOn();
						}
					} else if (mapArray[index] == "26" || mapArray[index] == "30") {
						var lightAnim6:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim6);
						var ues:EarthSource = new EarthSource(pos.x, pos.y,lightAnim6);
						UnlitSources.add(ues);
						tileMap[index] = ues;
						if (mapArray[index] == "30") {
							ues.turnOn();
						}
					} else if (mapArray[index] == "27" || mapArray[index] == "31") {
						var lightAnim7:FlxSprite = new FlxSprite(pos.x - 23, pos.y - 26, Res.blankImage);
						SourceLightAnims.add(lightAnim7);
						var uls:LightSource = new LightSource(pos.x, pos.y,lightAnim7);
						UnlitSources.add(uls);
						tileMap[index] = uls;
						if (mapArray[index] == "31") {
							uls.turnOn();
						}
					} else if (mapArray[index] == "99") {
						playerImageSprite = new FlxSprite(j*CELL_SIZE + X_OFFSET, i*CELL_SIZE + Y_OFFSET, null);
						player = new Player(j*CELL_SIZE + X_OFFSET, i*CELL_SIZE + Y_OFFSET, playerImageSprite);
						tileMap[index] = "player";
					} else if (mapArray[index] == "98") {
						treasure = new Point(pos.x, pos.y);
						// todo: modify treasure to be a flx obj
						tileMap[index] = "treasure";
					} else if (mapArray[index] == "97" || mapArray[index] == "96") {
						if (mapArray[index] == "97") {
							house = new PoofHouse(pos.x - 43, pos.y - 46, false);
						} else {
							house = new PoofHouse(pos.x - 43, pos.y - 46, true);
						}
						var p1:Point = getPointFromIndex((i-1)*COLS + j);
						var p2:Point = getPointFromIndex(i*COLS + (j-1));
						var p3:Point = getPointFromIndex((i-1)*COLS + (j-1));
						var b1:FlxTileblock;
						var b2:FlxTileblock;
						var b3:FlxTileblock;
						var b4:FlxTileblock;
						
						b1 = new FlxTileblock(p1.x, p1.y, CELL_SIZE,CELL_SIZE);
						b2 = new FlxTileblock(p2.x, p2.y, CELL_SIZE,CELL_SIZE);
						b3 = new FlxTileblock(p3.x, p3.y, CELL_SIZE,CELL_SIZE);
						b4 = new FlxTileblock(pos.x, pos.y, CELL_SIZE,CELL_SIZE);
						Blocks.add(b1);
						Blocks.add(b2);
						Blocks.add(b3);
						Blocks.add(b4);
						tileMap[index] = b1;
						tileMap[index - 1] = b2;
						tileMap[index - 11] = b3;
						tileMap[index - 12] = b4;
						Houses.add(house);
					} else if (int(mapArray[index]) >= 50 && int(mapArray[index]) <= 69){
						if (int(mapArray[index]) == 56) {
							var pit:FlxTileblock;
							pit = new FlxTileblock(pos.x, pos.y, CELL_SIZE,CELL_SIZE);
							pit.loadTiles(Res.pitImages, 46, 46, 0);
							Ground.add(pit);
						}
						var type:int = int(mapArray[index])-50;
						var obsImage:FlxSprite = new FlxSprite(pos.x, pos.y, null);
						var o:Obstacle = new Obstacle(pos.x, pos.y, type, obsImage);
						
						obsImages.add(obsImage);
						obs.add(o);
						//Allows beams to travel thrugh obstacles and didn't look like
						//we were using tilemap for anything else so...
						tileMap[index] = o;
					} else if (int(mapArray[index]) >= 70 && int(mapArray[index]) <= 90) {
						var monster:Monster = new Monster(new Point(pos.x, pos.y), int(mapArray[index]) - 70);
						Monsters.add(monster);
					} else if (mapArray[index] == "101") {
						var firstNPC:NPC = new NPC(pos.x, pos.y, theNPCs[0], theNPCs[1], "");
						NPCGroup.add(firstNPC);
						tileMap[index] = firstNPC;
					} else if (mapArray[index] == "102") {
						var secondNPC:NPC = new NPC(pos.x, pos.y, theNPCs[2], theNPCs[3], "");
						NPCGroup.add(secondNPC);
						tileMap[index] = secondNPC;
					} else if (mapArray[index] == "103") {
						var thirdNPC:NPC = new NPC(pos.x, pos.y, theNPCs[4], theNPCs[5], "");
						NPCGroup.add(thirdNPC);
						tileMap[index] = thirdNPC;
					} else if (mapArray[index] == "104") {
						var exitingNPCOne:NPC = new NPC(pos.x, pos.y, theNPCs[0], "", theNPCs[1]);
						ExitingNPCGroup.add(exitingNPCOne);
					} else if (mapArray[index] == "105") {
						var exitingNPCTwo:NPC = new NPC(pos.x, pos.y, theNPCs[2], "", theNPCs[3]);
						ExitingNPCGroup.add(exitingNPCTwo);
					}  else if (mapArray[index] == "300") {
						FinalBoss = new FlxSprite(pos.x, pos.y);
						FinalBoss.loadGraphic(Res.BossImage, true, false, 138, 138);
					}
				}
			}
			
			if (Monsters.length > 0 ) {
				pacifist = true;
			}
			
			//Construct the Train
			train = new Train(player);
			for (var k:int = 0; k < trainString.length; k++) {
				if (trainString[k] == "1") {
					var chicken:Chicken = new Chicken(player.x, player.y, null);
					train.addAnimal(chicken);
					collideTrain.add(chicken);
				} else if (trainString[k] == "2") {
					var goat:Goat = new Goat(player.x, player.y, null);
					train.addAnimal(goat);
					collideTrain.add(goat);
				}
			}
			
			this.add(Ground);
			this.add(obs);
			this.add(obsImages);
			this.add(Blocks);
			this.add(Tiles);
			this.add(Forges);
			this.add(SourceLightAnims);
			this.add(Sources);
			this.add(UnlitSources);
			this.add(Beams);
			this.add(arrowGroup);
			//beams and arrows
			bin = new ArrowBin(GRID_WIDTH, 0, 4, 1, ArrowArray);
			this.add(bin);
			
			this.add(collideTrain);
			this.add(NPCGroup);
			this.add(ExitingNPCGroup);
			this.add(TransformSprites);
			this.add(player);
			this.add(playerImageSprite);
			this.add(Monsters);
			this.add(Houses);
			if(FinalBoss != null) {
				FinalBoss.addAnimation("colorShift", [0,1,2,3,4,3,2,1], 5);
				this.add(FinalBoss);
				FinalBoss.play("colorShift");
			}
			
			LevelComplete = new FlxSprite(0,0,Res.LevelCompleteImage);
			
			Complete = false;
			
			StoryText = new FlxText(55, 5, 455, "", false);
			StoryText.setFormat("serif", 25, 0xFF505050, "left", 0x00000000);
			NPCText = new FlxText(5, 520, 506, "", false);
			NPCText.setFormat("serif", 18, 0xFF000000, "left");
			this.add(NPCText);
			theOverlay = new FlxSprite(5,5,Res.overlay);
			
			//cheat sheet
			if (CurrentMap >= 5) {
				cheatButton = new FlxSprite(0, 0, Res.ComboButton);
				cheatButton.x = FlxG.width - cheatButton.width - 10;
				cheatButton.y = FlxG.height - cheatButton.height - 10;
				add(cheatButton);
				
				cheatSheet = new CheatSheet(CurrentMap);
			}
			
			if (OpeningText.length > 0) {
				this.add(theOverlay);
				this.add(StoryText);
				StoryNPC = new NPC(5,5,OpeningText[oCount],"", "");
				this.add(StoryNPC);
				oCount++;
				StoryText.text = OpeningText[oCount];
				oCount++;
				Elemental.FREEZE = true;
				
				// hack hack hack
				if (CurrentMap == 3) {
					inMapArrow = new BouncyArrow(350, 290, 90);
					add(inMapArrow);
				}
			} else {
				// this is when there IS not opening.
				starting = false;		
			}
			
			
			MonsterLock = true;
			log.startDquest(theMap);
		}
		
		public function updateDuringStart():void {
			if (FlxG.mouse.justPressed()) {
				if (inMapArrow != null) {
					remove(inMapArrow);
					inMapArrow = null;
				}
				// If we've finished all the opening text,
				// end the start phase and go into game phase.
				if (oCount >= OpeningText.length) {
					StoryText.text = "";
					remove(StoryNPC);
					var anIndex:int = ExitingNPCGroup.getFirstNull();
					if (anIndex == -1) {
						anIndex = ExitingNPCGroup.length;
					}
					for (var l:int = 0; l < anIndex; l++) {
						var anNPC:NPC = ExitingNPCGroup.members[l];
						anNPC.exit();
					}
					starting = false;
					Elemental.FREEZE = false;
					remove(theOverlay);
					remove(StoryText);
					
				} else {
					// Proceed to the next story.
					remove(StoryNPC);
					StoryNPC = new NPC(5,5,OpeningText[oCount],"", "");
					add(StoryNPC);
					oCount++;

					StoryText.text = OpeningText[oCount];
					oCount++;
					
					// hardcoded hack. (avert your eyes)
					if ((CurrentMap == 1) && ((oCount - 1) == OpeningText.length - 1)) {
						inMapArrow = new BouncyArrow(350, 130, 90);
						add(inMapArrow);
					}
				}
			}
		}
		
		
		public function updateDuringGameplay():void {
			//Collisions to calculate.
			MonsterLock = true;
			train.updateTrain();
			FlxG.overlap(obs,player,collideObs);
			FlxG.overlap(Forges, player, transform);
			FlxG.overlap(UnlitSources, player, lightSource);
			FlxG.overlap(NPCGroup, player, displayMessage);
			FlxG.collide(Blocks, player);
			FlxG.collide(Blocks, collideTrain, returnAnimal);
			FlxG.collide(Monsters, player, gameOver);
			if (player.element != Elemental.VOID) {
				FlxG.collide(Monsters, Blocks, monsterAI);
				FlxG.collide(Monsters, obs, monsterAI);
				FlxG.collide(Monsters, collideTrain, killMonster);
			}
			
			if (FinalBoss != null && !Complete) {
				FlxG.overlap(player, FinalBoss, EndGame);
			} else if (!Complete) {
				if (UnlitSources.getFirstExtant() == null) {
					if (LevelCompleteDelay > 0 || achievDisplayed) {
						LevelCompleteDelay--;
						Elemental.FREEZE = true;
					} else {
						// If there is ending dialogue
						if (EndingText.length > 0) {
							ending = true;
							add(theOverlay);
							if (CurrentMap == 5) {
								add(new BouncyArrow(490, 480, 45));
							}
							StoryNPC = new NPC(5,5,EndingText[eCount],"", "");
							add(StoryNPC);
							add(StoryText);
							eCount++;
							StoryText.text = EndingText[eCount];
							eCount++;
						} else {
							log.logLevelClear(String(CurrentMap));
							Complete = true;
							add(theOverlay);
							add(LevelComplete);
						}
					}
				}
			}
			if (player.element == Elemental.VOID) {
				var monsterIndex:int = Monsters.members.length;
				for (var m:int = 0; m < monsterIndex; m++) {
					var aMonster:Monster = Monsters.members[m];
					if (aMonster != null) {
						aMonster.toPlayer(new Point(player.x, player.y));
					}
				}
				if (collideTrain.getFirstExtant() != null) {
					for (var b:int = 0; b < 5; b++) {
						if (collideTrain.getFirstExtant != null) {
							collideTrain.remove(collideTrain.getFirstExtant());
						}
					}
					train.removeAll();
				}
				FlxG.overlap(player, Sources, turnOffSource);
			}
			if (RightArrowSprite != null) {
				if (FlxG.mouse.justPressed()) {
					if (!cheatSheetOpen) {
						this.remove(RightArrowSprite);
						RightArrowSprite = null;
					}
				} else {
					if (cheatSheetOpen) {
						ArrowCountDown = 500;
					} else if (ArrowCountDown == 0) {
						this.remove(RightArrowSprite);
						RightArrowSprite = null;
					} else {
						ArrowCountDown--;
					}
				}
			}
			
			if (FlxG.mouse.justPressed()) {
				if (Complete) {
					Elemental.FREEZE = false;
					log.logLevelClear(String(CurrentMap));
					
					if (FinalBoss != null)  {
						FlxG.switchState(new LootState(CurrentMap + 1));
					} else {
						FlxG.switchState(new GameState(CurrentMap + 1));
					}
					var level:String = localInformation.data["currentLevel"];
					localInformation.data["currentLevel"] = Math.max(int(level), CurrentMap + 1);
				}
				clearBeams();
				var index:int;
				index = getIndexFromPoint(player.x+player.width/2, player.y+player.height/2);
				if (arrowMap[index] == null 
					&& bin.overlapsPoint(FlxG.mouse.screenX, FlxG.mouse.screenY) 
					&& !(tileMap[index] is Obstacle)
					&& !(tileMap[index] is NPC)){
					var arrow:Arrow = bin.dispatchArrow(index);
					//log place arrow here
					if (arrow != null) {
						arrowGroup.add(arrow);
						arrowMap[index] = arrow;
						log.logArrowPlaced(index,arrow.getOrientation());
						this.arrowLastPlaced = index;
					}
				} else {
					index = getIndexFromPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
					if (arrowMap[index] != null){
						//arrowMap[index] == null;
						arrowGroup = bin.reclaimArrow(arrowGroup,arrowMap);
					}
				}
				
				if (cheatSheet != null && cheatSheet.overlapsPoint(FlxG.mouse.screenX, FlxG.mouse.screenY) && members.indexOf(cheatSheet) != -1) {
					remove(cheatSheet);
					cheatSheetOpen = false;
					Elemental.FREEZE = false;
				} else if (cheatButton != null && cheatButton.overlapsPoint(FlxG.mouse.screenX, FlxG.mouse.screenY)) {
					if (!Music.MUTE){
						FlxG.play(Res.unscrollSound);
					}
					cheatSheet.prepare(false, CurrentMap);
					log.logCheatSheet();
					add(cheatSheet);
					Elemental.FREEZE = true;
				}
				
			} else {
				FlxG.overlap(arrowGroup, Sources, updateFlow);
				FlxG.overlap(arrowGroup, Beams, updateFlow);
			}
			
			if (NPCMessageUp) {
				if (MessageCounter < 0) {
					NPCMessageUp = false;
					NPCText.text = "";
				} else {
					MessageCounter--;
				}
			}
		}
		
		public function updateDuringEnd():void {
			if (FlxG.mouse.justPressed()) {
				if (eCount >= EndingText.length) {
					this.remove(StoryNPC);
					this.remove(StoryText);
					ending = false;
					Elemental.Log.logLevelClear(String(CurrentMap));
					Complete = true;
					this.add(LevelComplete);
				} else {
					this.remove(StoryNPC);
					StoryNPC = new NPC(5,5,EndingText[eCount],"", "");
					this.add(StoryNPC);
					eCount++;
					StoryText.text = EndingText[eCount];
					eCount++;
				}
			}
		}
		
		public override function update():void {
			super.update();		
			
			Logging.time += FlxG.elapsed;
			
			if (FlxG.keys.R) {
				FlxG.switchState(new GameState(CurrentMap));
			}
			
			if (starting) {
				updateDuringStart();
			} else if (ending) {	
				updateDuringEnd();
			} else {
				updateDuringGameplay();
			}
			
			
			playerLastX = player.x;
			playerLastY = player.y;
			
			musicIcon.updateMusic(music);
			FlxG.collide(ExitingNPCGroup, Blocks, removeNPC);
			FlxG.overlap(ExitingNPCGroup, UnlitSources, turnOffSourceNPC);
			
			//Updates whether the game is muted or not.
			if (FlxG.mouse.justPressed() && replayIco.overlapsPoint(FlxG.mouse.screenX, FlxG.mouse.screenY)) {
				FlxG.switchState(new GameState(CurrentMap));
			}
			if (FlxG.mouse.justPressed() && ExitButton.overlapsPoint(FlxG.mouse.screenX, FlxG.mouse.screenY)) {
				FlxG.switchState(new LootState(CurrentMap));
			}
			updateAchievementDisplay();
		}
		
		/**
		 * handles everything with displaying an achievement in game
		 */
		private function updateAchievementDisplay():void {
			if (Elemental.MODE != Elemental.FACEBOOK_MODE) {
				return;
			}
			
			if (LevelCompleteDelay == 99) {
				Elemental.achievements.updateStoryLevel(CurrentMap);
				if (pacifist) {
					Elemental.achievements.updatePacifist();
				}
			}
			if (!achievDisplayed) {
				if (Elemental.achievements.hasAchievementToReport()) {
					achievCurr = Elemental.achievements.getNextAchievementToReport();
					this.add(achievCurr);
					achievesTimer = 0;
					achievDisplayed = true;
				}
			} else if (achievesTimer > 2.2) {
				this.remove(achievCurr, true);
				if (Elemental.achievements.hasAchievementToReport()) {
					achievCurr = Elemental.achievements.getNextAchievementToReport();
					this.add(achievCurr);
					achievesTimer = 0;
				} else {
					achievDisplayed = false;
					achievesTimer = 0;
				}
			} else {
				achievesTimer += FlxG.elapsed;
			}
		}
		
		/**
		 * If the end game winning conditions are met (if the player gets the chest).
		 **/
//		private function win(obj1:FlxObject, obj2:FlxObject):void {
//			Elemental.Log.logLevelClear(String(CurrentMap));
//			
//			FlxG.switchState(new LootState(CurrentMap)); 
//		}
		
		/**
		 * If the player loses (If the player collides with a monster).
		 **/
		private function gameOver(obj1:Monster, obj2:FlxObject):void {
			if (player.element == Elemental.VOID) {
				Monsters.remove(obj1);
				pacifist = false;
				if (Elemental.MODE == Elemental.FACEBOOK_MODE) {
					Elemental.achievements.updateKill();
				}
			} else {
				log.logkillByMonster(obj1.x,obj1.y,CurrentMap+"");
				FlxG.switchState(new LoseState(CurrentMap));
			}
		}
		
		/**
		 * Removes the monster and the animal that killed it from the game. Also
		 * adjusts the train.
		 **/
		private function killMonster(monster:Monster, animal:TrainAnimal):void {
			if(MonsterLock) {
				log.logMonsterKilled(monster.x,monster.y)
				MonsterLock = false;
				animal.dead = true;
				train.remove();
				collideTrain.remove(animal);
				Monsters.remove(monster);
				pacifist = false;
				if (Elemental.MODE == Elemental.FACEBOOK_MODE) {
					Elemental.achievements.updateKill();
				}
			}
		}
		
		/**
		 * Tells the monster what to do if it collides with a wall.
		 **/
		private function monsterAI(monster:Monster, obj1:FlxObject):void {
			monster.changeDirection();
		}
		
		/**
		 * The collision function for animals and walls. If the animal hits a wall
		 * it is set on a path to return to the train. If it collides a second
		 * time while returning it simply is teleported back to the train.
		 * 
		 * Not the greatest, but functional for now. 
		 **/
		private function returnAnimal(obj1:FlxObject, animal:TrainAnimal):void {
			if (animal.returning) {
				animal.returning = false;
				animal.thrown = false;
			} else {
				animal.returning = true;
				animal.stopThrowingAnim();
			}
		}
		
		/**
		 *  collision behavior between a player and an obstacle
		 */
		private function collideObs(obj1:FlxObject, obj2:FlxObject):void{
			var player:Player = obj2 as Player;
			var obs:Obstacle = obj1 as Obstacle;
			

			if (!obs.typeMatch(player) && player.element != Elemental.VOID) {
				obs.playElementsAnimation();
				player.x = playerLastX;
				player.y = playerLastY;
			} else {
				obs.playPassableAnimation();
				/*var currentIndex:int = getIndexFromPoint(player.x,player.y);
				var lastIndex:int = getIndexFromPoint(playerLastX,playerLastY);
				var condOne:Boolean = tileMap[lastIndex] != null;
				var condTwo:Boolean = !(tileMap[lastIndex] is Obstacle);
				var condThree:Boolean = 
				
				if ( (tileMap[player.lastIndex] != null) && !(tileMap[player.lastIndex] is Obstacle)  ){
				trace(tileMap[player.lastIndex].toString());
				//log.logObstacleCrossed(player);
				}*/
			}
		}
		
		private function EndGame(obj1:FlxObject, obj2:FlxObject):void {
			if (player.element != Elemental.VOID) {
				FlxG.switchState(new LoseState(CurrentMap));
			} else {
				if (LevelCompleteDelay > 0 || achievDisplayed) {
					LevelCompleteDelay--;
					Elemental.FREEZE = true;
				} else {
					if (!ending) {
						ending = true;
						this.add(theOverlay);
						StoryNPC = new NPC(5,5,EndingText[eCount],"", "");
						this.add(StoryNPC);
						this.add(StoryText);
						eCount++;
						StoryText.text = EndingText[eCount];
						eCount++;
					}
				}
			}
		}
		
		private function lightSource(theSource:PlumeSource, player:Player):void {
			if (theSource.element == player.element) {
				Sources.add(UnlitSources.remove(theSource, true));
				theSource.turnOn();
			}
		}
		
		/**
		 * If an arrow if placed on an beam or source, check to see
		 * if we should update the flow.
		 */
		private function updateFlow(obj1:FlxObject, obj2:FlxObject):void {
			// Flixel is mean and switches params on you for fun.
			var arr:Arrow;
			var target:FlxObject;
			if (obj1 is Arrow) {
				arr = obj1 as Arrow;
				target = obj2;
			} else {
				arr = obj2 as Arrow;
				target = obj1;
			}
			
			if (arr.isClicked()) {
				return;
			}
			
			if (!Music.MUTE && arrowLastPlaced == getIndexFromPoint(arr.x,arr.y)){
				FlxG.play(Res.onSound);
				arrowLastPlaced = -1;
			}
			
			var direction:int = arr.getOrientation();
			
			if (target is PlumeSource) {
				var source:PlumeSource = target as PlumeSource;
				if (!source.on) {
					arr.setAssociation(source.element);
					drawBeam(source, getIndexFromPoint(source.x, source.y), source.element, direction);
				}
			} else if (target is SourceBeam) {
				var beam:SourceBeam = target as SourceBeam;
				if (!beam.connected) {
					arr.setAssociation(beam.element);
					drawBeam(beam, getIndexFromPoint(arr.x, arr.y), beam.element, direction);
				} 
			}
		}
		
		/**
		 * Transforms player into a new element, if sourceforge is lit.
		 * Called when player walks into a sourceforge.
		 */
		private function transform(forge:SourceForge, p:Player):void {
			if (forge.element != p.element) {
				var playerPos:FlxPoint = p.getMidpoint();
				var fPos:FlxPoint = forge.getMidpoint();
				if (Math.abs(playerPos.x - fPos.x) <= 10 &&
					Math.abs(playerPos.y - fPos.y) <= 10 &&
					forge.isLit()) {
					forge.transformationAnim();
					p.transform(forge.element); 
				}
				if (forge.element == Elemental.VOID){
					music = Res.voidMusic;
				}
			}
		}
		
		/**
		 * Draws the longest beam that can be emitted from the specified source
		 * in the specified direction.
		 * Current version: beams collide only with walls and sourceforges.
		 */
		public function drawBeam(startObj:FlxSprite, startIndex:int, element:int, direction:int):void {
			var index:int = stepToNext(startIndex, direction);
			var item:Object;
			var length:int = 0;
			var forge:SourceForge;
			var theSource:PlumeSource;
			var spawnNew:Boolean = false; // same for starting on arrow.
			
			while (index >= 0) {
				item = getItemAtIndex(index);
				if (item is FlxTileblock && !(item is Obstacle)) {
					break;
				} else if (item is SourceForge) {
					forge = item as SourceForge;
					forge.addSource(element);
					break;			
				} else if (arrowMap[index] is Arrow) {
					var newArr:Arrow = arrowMap[index] as Arrow;
					if (Direction.getOpposite(newArr.getOrientation()) == direction) {
						break;
					}
					if(newArr.isAssociatedWith(element)) {
						break;
					} else {
						//increasing the length by 1 if there's a new arrow
						//solves the problem and overlaps the arrow: Not bad I hope.
						newArr.setAssociation(element);
						spawnNew = true;
						length++;
					}
					break;
				} else if (item is PlumeSource) {
					theSource = item as PlumeSource;
					if (!theSource.isLightOn()) {
						if (theSource.element == element) {
							if (!voidIsOnTheSource(theSource)) {
								theSource.turnOn();
								Sources.add(UnlitSources.remove(theSource, true));
							}
						}
					}
				}
				length++;
				index = stepToNext(index, direction);
			}
			
			if (length > 0 || forge != null) {
				var startsOnArrow:Boolean = arrowMap[startIndex] != null;
				var beam:SourceBeam = new SourceBeam(startIndex, forge, startsOnArrow, spawnNew, element, length, direction);
				Beams.add(beam);
				
				if (startObj is PlumeSource) {
					var start:PlumeSource = startObj as PlumeSource;
					start.setBeam(beam, forge);
				} else if (startObj is SourceBeam) {
					var startBeam:SourceBeam = startObj as SourceBeam;
					startBeam.connected = true;
				}
				
				// Our beam has hit a new arrow, so we need to draw a new beam.
				if (spawnNew) {
					var arr:Arrow = arrowMap[index]
					drawBeam(beam, index, element, arr.getOrientation());
				}
			} 
		}
		
		/**
		 * Removes all beams from screen, resets sources to off.
		 */
		private function clearBeams():void {
			var array:Array = Beams.members;
			var oldLength:int = array.length;
			for (var i:int = 0; i < oldLength; i++) {
				var beam:SourceBeam = array[0] as SourceBeam;
				if (beam != null) {
					beam.disconnect();
				}
				array.shift();
			}
			var cutOff:int = Sources.getFirstNull();
			if (cutOff == -1) {
				cutOff = Sources.length;
			}
			for (var j:int = 0; j < cutOff; j++) {
				var s:PlumeSource = Sources.members[j] as PlumeSource;
				s.removeBeam();
			}
			
			var ArrowArray:Array = arrowGroup.members;
			var theLength:int = ArrowArray.length;
			for (var k:int = 0; k < theLength; k++) {
				if (ArrowArray[k] is Arrow) {
					var arrow:Arrow = ArrowArray[k] as Arrow;
					arrow.clearAssociations();
				}
			}
		}
		
		/**
		 * Returns cell index that contains this point.
		 */
		public static function getIndexFromPoint(x:int, y:int):int {
			var row:int = (y - Y_OFFSET) / CELL_SIZE;
			var col:int = (x - X_OFFSET) / CELL_SIZE;
			return (row * COLS) + col;
		}
		
		/**
		 * Returns upper left point of cell index.
		 */
		public static function getPointFromIndex(index:int):Point {
			var row:int = index / COLS;
			var col:int = index - (row * COLS); 
			return new Point(col * CELL_SIZE + X_OFFSET, row * CELL_SIZE + Y_OFFSET);
		}
		
		/**
		 * Returns the item located at an index
		 * Assumes each index carries only one item at most (probably bad)
		 */
		private function getItemAtIndex(index:int):Object {
			return tileMap[index];
		}
		
		/**
		 * Returns the neighbor index in the specified direction.
		 * -1 is returned if it's an out-of-bounds cell.
		 */
		private function stepToNext(startIndex:int, direction:int):int {
			var nextIndex:int;
			if (direction == Direction.NORTH) {
				nextIndex = startIndex - COLS;
				if (nextIndex < 0) {
					return -1;
				}
			} else if (direction == Direction.SOUTH) {
				nextIndex = startIndex + COLS;
				if (nextIndex > (COLS * ROWS) - 1) {
					return -1;
				}
			} else if (direction == Direction.EAST) {
				if ((startIndex + 1) % COLS == 0) {
					return -1;
				}
				nextIndex = startIndex + 1;
			} else {
				if ((startIndex ) % COLS == 0) {
					return -1;
				}
				return startIndex - 1;
			}
			return nextIndex;
		}
		
		/**
		 * Given an index, returns the midpoint of that grid cell.
		 */
		private function getMidpointOfIndex(index:int):Point {
			var p:Point = getPointFromIndex(index);
			return new Point(p.x + (CELL_SIZE / 2), p.y + (CELL_SIZE / 2));
		}
		
		private function displayMessage(theNPC:NPC, player:Player):void {
			MessageCounter = 10;
			NPCMessageUp = true;
			if (theNPC.message != NPCText.text) {
				NPCText.text = theNPC.message;
			}
		}
		
		private function removeNPC(theNPC:NPC, obj1:FlxObject):void {
			ExitingNPCGroup.remove(theNPC);
		}
		
		private function turnOffSource(player:Player, theSource:PlumeSource):void {
			if (theSource.isLightOn()) {
				theSource.turnOff();
				Sources.remove(theSource, true);
				UnlitSources.add(theSource);
				clearBeams();
			}
		}
		
		private function turnOffSourceNPC(anNPC:NPC, theSource:PlumeSource):void {
			if (theSource.isLightOn()) {
				theSource.turnOff();
			}
		}
		
		private function voidIsOnTheSource(theSource:PlumeSource):Boolean {
			if (player.element != Elemental.VOID) {
				return false;
			} else {
				var sMP:FlxPoint = theSource.getMidpoint();
				var pMP:FlxPoint = player.getMidpoint();
				return  (Math.abs(sMP.x - pMP.x) < 33 && Math.abs(sMP.y - pMP.y) < 33);
			}
		} 
		
		
		/**
		 * Special cheat sheet display, when a new rule is introduced
		 */
		private function displayCheatSheet():void {
			if (cheatSheet != null && cheatSheet.containsRuleForLevel(CurrentMap)) {
				cheatSheetOpen = true;
				if (!Music.MUTE) {
					FlxG.play(Res.unscrollSound);
				}
				cheatSheet.prepare(true, CurrentMap);
				add(cheatSheet);
				Elemental.FREEZE = true;
			} else {
				displayHintArrow();
			}
		}
		
		private function displayHintArrow():void {
			Elemental.FREEZE = false;
			if (Elemental.TText[CurrentMap].length > 0 && CurrentMap <= 5) {
				RightArrowSprite = new BouncyArrow(700-90, 95, 90);
				add(RightArrowSprite);
				ArrowCountDown = 500;
			}
		}
	}
}
